using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using SolarWinds.Helpers;
using SolarWinds.Input;
using SolarWinds.Transitions;

namespace SolarWinds.Screens
{
    public class ScreenManager : DrawableComponentManager, IScreenManagerService
    {
        private readonly List<Screen> mActiveScreens;
        private readonly List<Transition> mActiveTransitions;

        private IInputManagerService mInputService;
        private SpriteBatch mSpriteBatch;

        #region Constructor

        public ScreenManager(Game game)
            : base(game)
        {
            game.Services.AddService(typeof(IScreenManagerService), this);

            mActiveScreens = new List<Screen>();
            mActiveTransitions = new List<Transition>();
        }

        #endregion

        #region Initialize / Dispose

        public override void Initialize()
        {
            mInputService = (IInputManagerService)
                            Game.Services.GetService(typeof(IInputManagerService));

            InstallHandlers();

            base.Initialize();
        }

        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);

            if (disposing)
            {
                RemoveHandlers();
                ClearScreens();

                if (Game != null)
                {
                    Game.Services.RemoveService(typeof(IScreenManagerService));
                }
            }
        }

        #endregion

        #region Install / Remove Keyboard EventHandlers

        private void InstallHandlers()
        {
            mInputService.Keyboard.KeyClicked += OnKeyClicked;
        }

        private void RemoveHandlers()
        {
            mInputService.Keyboard.KeyClicked -= OnKeyClicked;
        }

        private static void OnKeyClicked(object sender, KeyEvent e)
        {
            if (e.Key == Microsoft.Xna.Framework.Input.Keys.Escape)
            {
                // TODO cancel all transitions
                //if (mCurrentTransition != null)
                {
                    //  mCurrentTransition.Cancel();
                }
            }
        }

        #endregion

        #region PushScreen

        public bool PushScreen(Screen screen)
        {
            return PushScreen(screen, new FadeTransition(Game));
        }

        public bool PushScreen(Screen screen, Transition transition)
        {
            if (mActiveScreens.Contains(screen))
            {
                return false;
            }

            // disable user input during transition
            mInputService.Mouse.Enabled = false;
            mInputService.Keyboard.Enabled = false;

            // add the screen to components 
            Components.Add(screen);

            // configure transition and start it
            transition.Screen = screen;
            transition.Mode = Transition.BlendModes.In;
            transition.Ended += TransitionInEnded;
            Components.Add(transition);
            mActiveTransitions.Add(transition);
            transition.Start();

            // wait until the transition has ended.
            // The push process will then continue in the 
            // TransitionInEnded() method.

            return true;
        }

        void TransitionInEnded(object sender, EventArgs e)
        {
            var transition = (Transition)sender;
            Screen screen = transition.Screen;

            // screen is now active
            mActiveScreens.Add(screen);
            screen.Disposed += ScreenDisposed;

            // dispose the transition
            Components.Remove(transition);
            mActiveTransitions.Remove(transition);

            if (mActiveTransitions.Count <= 0)
            {
                mInputService.Mouse.Enabled = true;
                mInputService.Keyboard.Enabled = true;
            }
        }

        void ScreenDisposed(object sender, EventArgs e)
        {
            var screen = (Screen)sender;

            // Remove the screen from the manager
            Components.Remove(screen);
            mActiveScreens.Remove(screen);
            screen.Disposed -= ScreenDisposed;
        }

        #endregion

        #region PopScreen

        public Screen PopScreen()
        {
            return PopScreen(true);
        }

        public Screen PopScreen(bool dispose)
        {
            return PopScreen(new FadeTransition(Game), dispose);
        }

        public Screen PopScreen(Transition transition, bool dispose)
        {
            if (mActiveScreens.Count <= 0)
            {
                return null;
            }

            // disable user input during transition
            mInputService.Mouse.Enabled = false;
            mInputService.Keyboard.Enabled = false;

            // get the screen which ought to be removed
            Screen screen = mActiveScreens[mActiveScreens.Count - 1];

            if (transition == null)
            {
                // remove the screen without transition
                mActiveScreens.Remove(screen);
                Components.Remove(screen);
                screen.RemoveEventHandlers();
                screen.Disposed -= ScreenDisposed;

                if (dispose)
                {
                    screen.Dispose();
                }

                // enable user input
                if (mActiveTransitions.Count <= 0)
                {
                    mInputService.Mouse.Enabled = true;
                    mInputService.Keyboard.Enabled = true;
                }
            }
            else
            {
                // configure the transition and start it
                transition.Mode = Transition.BlendModes.Out;
                transition.Screen = screen;
                transition.DisposeFlag = dispose;
                transition.Ended += TransitionOutEnded;
                Components.Add(transition);
                mActiveTransitions.Add(transition);
                transition.Start();

                // screen is no longer active
                mActiveScreens.Remove(screen);
                screen.RemoveEventHandlers();
                screen.Disposed -= ScreenDisposed;

                // wait until the transition has ended.
                // The push process will then continue in the 
                // TransitionOutEnded() method.
            }
            
            if (dispose)
            {
                return null;
            }
            
            return screen;
        }

        void TransitionOutEnded(object sender, EventArgs e)
        {
            var transition = (Transition) sender;
            Screen screen = transition.Screen;
            bool dispose = transition.DisposeFlag;

            // dispose the transistion
            Components.Remove(transition);
            mActiveTransitions.Remove(transition);
            transition.Dispose();

            // remove the screen
            Components.Remove(screen);

            if (dispose)
            {
                screen.Dispose();
            }

            if (mActiveTransitions.Count <= 0)
            {
                mInputService.Mouse.Enabled = true;
                mInputService.Keyboard.Enabled = true;
            }
        }

        #endregion

        #region PeekScreen

        /// <summary>
        /// The topmost screen in the screen manager
        /// </summary>
        /// <returns>Topmost screen</returns>
        public Screen PeekScreen()
        {
            return mActiveScreens[0];
        }

        #endregion

        #region ClearScreens

        /// <summary>
        /// Clears the whole stack of active screens. By calling 
        /// this mehtod you will end up with a black screen.
        /// </summary>
        public void ClearScreens()
        {
            while (mActiveScreens.Count > 0)
            {
                Components.Remove(mActiveScreens[0]);
                mActiveScreens[0].Dispose();
            }

            mActiveScreens.Clear();
        }

        #endregion

        #region Update

        public override void Update(GameTime gameTime)
        {
            bool blockUpdate = false;
            for (int i = mActiveScreens.Count-1; i >= 0; --i)
            {
                Screen s = mActiveScreens[i];
                s.Enabled = !blockUpdate;

                // if we found the first blocking screen 
                // then block all following
                if (s.BlocksUpdate && !blockUpdate)
                {
                    blockUpdate = true;
                }
            }

            bool blockDraw = false;
            for (int i = mActiveScreens.Count - 1; i >= 0; --i)
            {
                Screen s = mActiveScreens[i];
                s.Visible = !blockDraw;

                // if we found the first blocking screen 
                // then block all following
                if (s.BlocksDraw && !blockDraw)
                {
                    blockDraw = true;
                }
            }

            Vector4 screenColor = Color.White.ToVector4();
            foreach (Screen s in mActiveScreens)
            {
                s.Color = new Color(screenColor);

                screenColor.X -= s.ColorReduction;
                screenColor.Y -= s.ColorReduction;
                screenColor.Z -= s.ColorReduction;
            }

            // update all screens
            base.Update(gameTime);
        }

        #endregion

        #region Load \ Unload Content

        protected override void LoadContent()
        {
            base.LoadContent();

            mSpriteBatch = new SpriteBatch(GraphicsDevice);
        }

        protected override void UnloadContent()
        {
            base.UnloadContent();

            if (mSpriteBatch != null &&
                !mSpriteBatch.IsDisposed)
            {
                mSpriteBatch.Dispose();
                mSpriteBatch = null;
            }
        }

        #endregion

        #region Drawing

        public override void Draw(GameTime gameTime)
        {
            // this draws all the screen onto render targets
            base.Draw(gameTime);

            var viewportCenter = new Vector2(
                GraphicsDevice.Viewport.Height*0.5f,
                GraphicsDevice.Viewport.Width*0.5f);

            // we clear the screen, fetch all textures from 
            // render targets and draw them to sprites
            GraphicsDevice.Clear(ClearOptions.Target |
                                 ClearOptions.DepthBuffer,
                                 Color.TransparentBlack, 0.0f, 0);

            mSpriteBatch.Begin(SpriteBlendMode.AlphaBlend,
                               SpriteSortMode.Immediate,
                               SaveStateMode.SaveState);

            GraphicsDevice.RenderState.ScissorTestEnable = true;

            foreach (Screen screen in mActiveScreens)
            {
                DrawScreen(screen, viewportCenter);
            }

            foreach (Transition transition in mActiveTransitions)
            {
                DrawScreen(transition.Screen, viewportCenter);
            }

            mSpriteBatch.End();
        }

        private void DrawScreen(Screen screen, Vector2 viewportCenter)
        {
            if (screen.Visible)
            {
                GraphicsDevice.ScissorRectangle = screen.CropRectangle;

                mSpriteBatch.Draw(screen.RenderedTexture, viewportCenter + screen.Position,
                                  null, new Color(screen.Color.ToVector4() * screen.Alpha),
                                  screen.Rotation, viewportCenter, screen.Scale, 
                                  SpriteEffects.None, 1);
            }
        }

        #endregion
    }
}